//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2019-12-05     ZhangYihua   first version
//
// Description  : 
//################################################################################

module decim_n #(
parameter           CDW                     = 16,
parameter           CFW                     = 15,
parameter           IDW                     = 12,
parameter           IFW                     = 9,
parameter           ODW                     = 12,
parameter           OFW                     = 9,
parameter           DEC_NUM                 = 2,
parameter           TAP_NUM                 = 15,       // must TAP_NUM>DEC_NUM
parameter           COE_NUM                 = TAP_NUM/2 + TAP_NUM%2,
parameter           COE_ZERO_MAP            = {COE_NUM{1'b0}},  // for saving area
parameter           COE_ZERO_NUM            = bit_sum_f(COE_ZERO_MAP),
parameter           PRDCT_NUM               = COE_NUM-COE_ZERO_NUM,
parameter           LVL_NUM                 = $clog2(PRDCT_NUM),
parameter [LVL_NUM-1:0]     LVL_REG         = {LVL_NUM{1'b0}}   // specify register for each level
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,
input                                       clk_nd,     // rising edge of clk_nd is aligned to rising/falling edge of clk
input                                       cke_nd,

input       signed  [IDW-1:0]               in_dat,

input               [COE_NUM*CDW-1:0]       in_coe,     // {CM-1, .... C1, C0}, M=COE_NUM
output      signed  [ODW-1:0]               out_dat 
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          PRDCT_DW                = IDW+1+CDW;
localparam          SUM_DW                  = PRDCT_DW+$clog2(PRDCT_NUM);

reg         signed  [IDW-1:0]               pipe_dat[TAP_NUM-1:0];
wire        signed  [IDW+1-1:0]             pre_add[COE_NUM-1:0];
wire        signed  [CDW-1:0]               coe_ary[COE_NUM-1:0];
reg         signed  [PRDCT_DW-1:0]          prdct_ary[COE_NUM-1:0];
reg                 [PRDCT_NUM*PRDCT_DW-1:0]prdct_nz;
wire                                        vld_out_nc;
wire        signed  [SUM_DW-1:0]            sum;
//################################################################################
// main
//################################################################################

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        pipe_dat[0] <=`U_DLY {IDW{1'b0}};
    end else if (cke==1'b1) begin
        pipe_dat[0] <=`U_DLY in_dat;
    end else
        ;
end

genvar g0;
generate 
    for (g0=1; g0<DEC_NUM; g0=g0+1) begin:G_PIPE0
        always@(posedge clk or negedge rst_n) begin
            if (rst_n==1'b0) begin
                pipe_dat[g0] <=`U_DLY {IDW{1'b0}};
            end else if (cke==1'b1) begin
                pipe_dat[g0] <=`U_DLY pipe_dat[g0-1];
            end else
                ;
        end
    end 

    for (g0=DEC_NUM; g0<TAP_NUM; g0=g0+1) begin:G_PIPE1
        always@(posedge clk_nd or negedge rst_n) begin
            if (rst_n==1'b0) begin
                pipe_dat[g0] <=`U_DLY {IDW{1'b0}};
            end else if (cke_nd==1'b1) begin
                pipe_dat[g0] <=`U_DLY pipe_dat[g0-DEC_NUM];
            end else
                ;
        end
    end 
endgenerate

genvar g1;
generate for (g1=0; g1<COE_NUM; g1=g1+1) begin:G_ADD_MUL
    if ((TAP_NUM%2==1) && (g1==(COE_NUM-1))) begin:G_ODD
        assign pre_add[g1] = pipe_dat[g1];
    end else begin:G_PAIR
        assign pre_add[g1] = pipe_dat[g1] + pipe_dat[TAP_NUM-1-g1];
    end
    
    assign coe_ary[g1] = in_coe[g1*CDW+:CDW];
    always@(posedge clk_nd or negedge rst_n) begin
        if (rst_n==1'b0) begin
            prdct_ary[g1] <=`U_DLY {PRDCT_DW{1'b0}};
        end else if (cke_nd==1'b1) begin
            prdct_ary[g1] <=`U_DLY pre_add[g1]*coe_ary[g1];
        end else
            ;
    end
end endgenerate

always@(*) begin:PICK_NZ
    integer         s;
    integer         i;
    
    s = 0;
    for (i=0; i<COE_NUM; i=i+1) begin
        if (COE_ZERO_MAP[i]==1'b0) begin
             prdct_nz[s*PRDCT_DW+:PRDCT_DW] = prdct_ary[i];
             s = s + 1;
        end
    end
end

add_all #(
        .OPRD_BW                        (PRDCT_DW                       ),	// bit width of single operand
        .OPRD_NUM                       (PRDCT_NUM                      ),	// number of operands, must 2<=OPRD_NUM<=16
        .OPRD_SIGNED                    (1'b1                           ),	// 1'b0: unsigned; 1'b1: signed;
        .LVL_NUM                        (LVL_NUM                        ),
        .LVL_REG                        (LVL_REG                        )
) u_add_all ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk_nd                         ),
        .cke                            (cke_nd                         ),

        .vld_in                         (1'b1                           ),
        .oprds                          (prdct_nz                       ),

        .vld_out                        (vld_out_nc                     ),
        .sum_o                          (sum                            )
);

s_sat_tru_reg #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (SUM_DW                         ),	// input data width
        .IFW                            (IFW+CFW                        ),	// input fractional width
        .ODW                            (ODW                            ),	// output data width
        .OFW                            (OFW                            )	// output fractional width
) u_out_dat ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk_nd                         ),
        .cke                            (cke_nd                         ),

        .id                             (sum                            ),	// s(IDW, IFW), the MSB is sign
        .od                             (out_dat                        ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
);

function integer bit_sum_f;
    input [COE_NUM-1:0]     vect;

    integer t;
    integer i;
    begin
        t = 0;
        for (i=0; i<COE_NUM; i=i+1) begin
            t = t + vect[i];
        end

        bit_sum_f = t;
    end
endfunction

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
